/*
 * cdma_simware.h -- header file of functions for cdma_simwaretrum gsm module
 *
 * Copyright (C) 2007 lufang <lufang@gmail.com>
 *
 * Version 1.0 june 2007
 *
 * This program is free software; you can redistribute it and/or
 * modify it under the terms of the GNU General Public License
 * as published by the Free Software Foundation; either version 2
 * of the License, or (at your option) any later version.
 *
 * This program is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 * GNU General Public License for more details.
 * 
 * You should have received a copy of the GNU General Public License
 * along with this program; if not, write to the Free Software
 * Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA  02111-1307, USA.
 *
 */

#include <stdlib.h>
#include <string.h>
#include <stdio.h>
#include <assert.h>
#include <unistd.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <fcntl.h>
#include <sys/ioctl.h>
#include <termios.h>
#include <errno.h>

#include "gsm0710.h"
#include "buffer.h"
#include "utils.h"
#include "ril_util.h"
#include "modem_usb.h"

#define DATA_VIR_PREFIX	"/dev/data"
#define DATA_PROXY_PER_LEN	100
#define CMD_RESET_DATA                      "RESET_DATA"
#define MUX_CMD_RESP_OK         "OK"

typedef enum SerialType
{
	SERIAL_PHY,
	SERIAL_USB
}SerialType;

typedef struct _DataProxy
{
	pthread_t       thread_id;

	ModemUsb*       usb;
	int				usb_index;
	int				vid;
	int				pid;
	
	SerialType		serial_type;
	char serial_path[32];
	char ctrl_path[32];
	int serial_fd;
	int ussp_fd;
}DataProxy;


static int data_proxy_send_command(DataProxy* thiz, const char* cmd);


static void data_proxy_reset_serial(DataProxy* thiz)
{
	if(thiz->serial_type == SERIAL_USB)
	{
		modem_usb_reset(thiz->usb);
	}
	else
	{
		data_proxy_send_command(thiz, CMD_RESET_DATA);
	}

	return;
}

static void data_proxy_refresh_port(DataProxy* thiz)
{
	GSMMUX_PRINT("%s", __func__);
	
	if(thiz->serial_type != SERIAL_USB)
	{
		return;
	}

	if(thiz->usb == NULL)
	{
		thiz->usb = modem_usb_create(0x19f5, 0x9013);
//		thiz->usb = modem_usb_create(thiz->vid, thiz->pid);		
	}

	modem_usb_get_port_path(thiz->usb, thiz->usb_index, thiz->serial_path);

	return;
}

static DataProxy* data_proxy_create_with_usb_serial(int index, int vid, int pid)
{
	DataProxy* thiz = calloc(1, sizeof(DataProxy));
	
	thiz->serial_type = SERIAL_USB;
	thiz->usb_index = index;
	thiz->vid = vid;
	thiz->pid = pid;
	/*
	thiz->usb_index = 3;
	thiz->vid = 0x19f5;
	thiz->pid = 0x9013;
	*/

	return thiz;
}

static DataProxy* data_proxy_create(const char* serial_path)
{
	DataProxy* thiz = calloc(1, sizeof(DataProxy));
	
	thiz->serial_type = SERIAL_PHY;
	snprintf(thiz->serial_path, 31, "%s", serial_path);
	GSMMUX_PRINT("data_proxy_create: serial_path=%s", thiz->serial_path);

	return thiz;
}

static void data_proxy_destroy(DataProxy* thiz)
{
	free(thiz);
	
	return;
}

static int data_proxy_send_command(DataProxy* thiz, const char* cmd)
{
	int ret = 0;
	struct timeval tv ;
	int fd = -1;

	if((fd = ril_open_port(PORT_TYPE_PTY, "/dev/mux2")) > 0)
	{
		fd_set fds;
		char	resp[64] ={0};
		int		resp_len = 0;

		write(fd, cmd, strlen(cmd));
		FD_ZERO(&fds);
		FD_SET(fd, &fds);
		gettimeofday(&tv, (struct timezone *) NULL);
		tv.tv_sec = 30;
		tv.tv_usec = 0;
		select(fd + 1, &fds, NULL, NULL, &tv);
		if(FD_ISSET(fd, &fds))
		{
			if ((resp_len = read(fd, resp, 64)) > 0 && strncmp(resp, MUX_CMD_RESP_OK, strlen(MUX_CMD_RESP_OK)) == 0)
			{
				ret = 1;
			}
		}
		close(fd);
	}
	
	return ret;
}

static void data_proxy_reopen_virtual_device(DataProxy* thiz)
{
	if(thiz->ussp_fd != 0)
	{
		close_pty(thiz->ussp_fd, 0, DATA_VIR_PREFIX);
		thiz->ussp_fd = 0;
	}
	thiz->ussp_fd = open_pty (0, DATA_VIR_PREFIX);

	return;
}

static void data_proxy_reopen_serial_port(DataProxy* thiz)
{
	GSMMUX_PRINT("%s: ", __func__);
	data_proxy_reset_serial(thiz);
	data_proxy_refresh_port(thiz);
	if(thiz->serial_fd != 0)
	{
		close(thiz->serial_fd);
		thiz->serial_fd = 0;
	}
	thiz->serial_fd = ril_open_port(PORT_TYPE_PTY, thiz->serial_path);
	GSMMUX_PRINT("%s: serial_path:%s serial_fd:%d", __func__, thiz->serial_path, thiz->serial_fd);

	return;
}

static void data_proxy_init(DataProxy* thiz)
{
	data_proxy_reopen_virtual_device(thiz);
	data_proxy_reopen_serial_port(thiz);
	GSMMUX_PRINT("%s: seiral_fd=%d", __func__, thiz->serial_fd);	

	return;
}

static int data_proxy_write(int fd, const unsigned char* buf, int len)
{
	int ret = 0;
	int remain = len;
	int wrote = 0;
	int write_len = 0;
	
	/*
	while(remain > 0)
	{
		write_len = (remain > DATA_PROXY_PER_LEN) ? DATA_PROXY_PER_LEN : remain;
		wrote = write(fd, buf + ret, write_len);
		GSMMUX_PRINT("@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@");
		PrintBuffer("write to ussp :", buf+ret, write_len);
		usleep(100 * 1000);
		if(wrote <= 0)
		{
			break;
		}
		else
		{
			ret += write_len;
			remain -= write_len;
		}
	}
	*/
	do {
		ret = write(fd, buf, len);
		if(ret == -1) 
		{
			GSMMUX_PRINT("strerror: %s\n", strerror(errno));
		}
	} while (ret == -1 && (errno == EAGAIN || errno == EINTR));

	return ret;
}

static void data_proxy_handle_ussp_data(DataProxy* thiz)
{
	unsigned char buf[1024] = {0};
	int		max_read = 0;
	int		bytes_read = 0;
	int		wrote = 0;
	
#ifdef ENABLE_LOG
	GSMMUX_PRINT("=================%s==================", __func__);
#endif
	if ((bytes_read = read(thiz->ussp_fd, buf, 1024)) > 0)
	{
		//write_file("/data/data/gsmmux_serial_read.bin", (const char*)buf, bytes_read);
#ifdef ENABLE_LOG
		PrintBuffer("ussp_data:", buf, bytes_read);
#endif
		if((wrote = data_proxy_write(thiz->serial_fd, buf, bytes_read)) <= 0)
		{
			data_proxy_reopen_serial_port(thiz);
		}
	
#ifdef ENABLE_LOG
		GSMMUX_PRINT("%s: bytes_read=%d, wrote=%d", __func__, bytes_read, wrote);
#endif
	}
	else if(bytes_read == -1)
	{
		data_proxy_reopen_virtual_device(thiz);
		GSMMUX_PRINT("data_proxy_reopen_virtual_device");
	}

	return;
}

static void data_proxy_handle_serial_data(DataProxy* thiz)
{
	unsigned char buf[1024] = {0};
	int		max_read = 0;
	int		bytes_read = 0;
	int		wrote = 0;
#ifdef ENABLE_LOG	
	GSMMUX_PRINT("=================%s==================", __func__);
#endif
	if ((bytes_read = read(thiz->serial_fd, buf, 1024)) > 0)
	{
		//write_file("/data/data/gsmmux_serial_read.bin", (const char*)buf, bytes_read);
#ifdef ENABLE_LOG
		PrintBuffer("serial_data:", buf, bytes_read);
#endif
		
		if((wrote = data_proxy_write(thiz->ussp_fd, buf, bytes_read)) <= 0)
		{
			data_proxy_reopen_virtual_device(thiz);
		}

#ifdef ENABLE_LOG
		GSMMUX_PRINT("%s: bytes_read=%d, wrote=%d", __func__, bytes_read, wrote);
#endif
	}
	else if(bytes_read == -1)
	{
		sleep(1);
		data_proxy_reopen_serial_port(thiz);
		GSMMUX_PRINT("reopen serial_fd");
	}
#ifdef ENABLE_LOG
	GSMMUX_PRINT("%s bytes_read=%d", __func__, bytes_read);
#endif

	return;
}

static void* data_proxy_mainloop(void* param)
{
	DataProxy* thiz = (DataProxy*)param;
	
	data_proxy_init(thiz);
	while(1)
	{
		fd_set read_fds;
		int max_fd = 0;
		int i = 0;

		FD_ZERO(&read_fds);

		if(thiz->serial_fd > 0)
		{
			FD_SET (thiz->serial_fd, &read_fds);
			max_fd = thiz->serial_fd;
#ifdef ENABLE_LOG
			//GSMMUX_PRINT("%s: serial_fd=%d", __func__, thiz->serial_fd);
#endif
		}

		if(thiz->ussp_fd > 0)
		{
			FD_SET (thiz->ussp_fd, &read_fds);
			if(thiz->ussp_fd > max_fd)
			{
				max_fd = thiz->ussp_fd;
			}
#ifdef ENABLE_LOG
			//GSMMUX_PRINT("%s: ussp_fd=%d", __func__, thiz->ussp_fd);
#endif
		}

		select(max_fd + 1, &read_fds, NULL, NULL, NULL);
		if(FD_ISSET(thiz->serial_fd, &read_fds))
		{
			data_proxy_handle_serial_data(thiz);
		} 
		else if(FD_ISSET(thiz->ussp_fd, &read_fds))
		{
			data_proxy_handle_ussp_data(thiz);
		}
	}
	data_proxy_destroy(thiz);
	
	return NULL;
}

int main (int argc, char** argv)
{
	pthread_attr_t attr;
	DataProxy* thiz = NULL;
	
	sleep(40);

	if(argc == 2)
	{
		thiz = data_proxy_create(argv[1]);
	}
	else if(argc == 4)
	{
		thiz = data_proxy_create_with_usb_serial(atoi(argv[1]), 
						atoi(argv[2]), atoi(argv[3]));
	}
	else
	{
		return 0;
	}
	
	/*create module listen thread*/
	pthread_attr_init (&attr);
	pthread_attr_setdetachstate(&attr, PTHREAD_CREATE_DETACHED);
	pthread_create(&thiz->thread_id, &attr, data_proxy_mainloop, thiz);

	while(1)
	{
		sleep(1000000000);
	}
	
	return 0;
}
